package com.sfdk.graphics.engine.data.layers {
import com.sfdk.graphics.engine.api.ILayerInformation;
import com.sfdk.graphics.engine.api.IMapObject;
import com.sfdk.graphics.engine.api.ITileset;
import com.sfdk.graphics.engine.core.enums.LayerType;
import com.sfdk.graphics.engine.data.TileInfo;
import com.sfdk.log.Logger;
import com.sfdk.utils.Base64;
import com.sfdk.utils.IntPoint;

import flash.display.BitmapData;

import flash.geom.Rectangle;

import flash.utils.ByteArray;
import flash.utils.Dictionary;

public class Layer implements ILayerInformation {
    public function Layer(layerData:XML, associatedMap:IMapObject, layerType:LayerType) {
        _associatedMap = associatedMap;
        _layerType = layerType;
        _layerData = layerData;

        parseLayerData();
    }

    //-------------------------------------------------------
    //
    //  properties
    //
    //-------------------------------------------------------

    //----------------------------------
    // isVisible
    //----------------------------------

    /**
     * @private
     */
    protected var _isVisible:Boolean = true;

    public function get isVisible():Boolean {
        return _isVisible;
    }

    public function set isVisible(value:Boolean):void {
        _isVisible = value;
    }

    //----------------------------------
    // isLocked
    //----------------------------------

    /**
     * @private
     */
    protected var _isLocked:Boolean = true;

    public function get isLocked():Boolean {
        return _isLocked;
    }

    public function set isLocked(value:Boolean):void {
        _isLocked = value;
    }

    //----------------------------------
    // name
    //----------------------------------

    /**
     * @private
     */
    protected var _name:String;

    public function get name():String {
        return _name;
    }

    public function set name(value:String):void {
        _name = value;
    }

    //----------------------------------
    // associatedMap
    //----------------------------------

    /**
     * @private
     */
    protected var _associatedMap:IMapObject;

    public function get associatedMap():IMapObject {
        return _associatedMap;
    }

    //----------------------------------
    // layerType
    //----------------------------------

    /**
     * @private
     */
    private var _layerType:LayerType;

    public function get layerType():LayerType {
        return _layerType;
    }

    //----------------------------------
    // layerData
    //----------------------------------

    /**
     * @private
     */
    protected var _layerData:XML;

    public function get layerData():XML {
        return _layerData;
    }

    //----------------------------------
    // tileData
    //----------------------------------

    /**
     * @private
     */
    protected var _tileData:Vector.<Vector.<TileInfo>>;

    public function get tileData():Vector.<Vector.<TileInfo>> {
        return _tileData;
    }

    //----------------------------------
    // connexRegion
    //----------------------------------

    /**
     * @private
     */
    protected var _connexRegion:Vector.<Vector.<int>>;

    public function get connexRegion():Vector.<Vector.<int>> {
        return _connexRegion;
    }

    //----------------------------------
    // connexMap
    //----------------------------------

    /**
     * @private
     */
    private var _connexMap:Dictionary;

    //--------------------------------------------------------------------------
    //
    //  methods
    //
    //--------------------------------------------------------------------------

    //----------------------------------
    // parseLayerData
    //----------------------------------

    protected function parseLayerData():void {
        _tileData = new Vector.<Vector.<TileInfo>>();

        var x:int;
        var y:int;
        var totalMapHeight:int = _associatedMap.totalHeightInTile;
        var totalMapWidth:int = _associatedMap.totalWidthInTile;
        var upperLimit:int = _associatedMap.upperLimit;
        var leftLimit:int = _associatedMap.leftLimit;
        var lowerLimit:int = _associatedMap.lowerLimit;
        var rightLimit:int = _associatedMap.rightLimit;


        if (_layerData == null) {
            // No layer data to parse, we have to built it from scratch
            for (y = 0; y < totalMapHeight; y++)
            {
                if (y >= upperLimit && y < lowerLimit)
                    _tileData[y - upperLimit] = new Vector.<TileInfo>();
                for (x = 0; x < totalMapWidth; x++) {
                    if (y >= upperLimit && y < lowerLimit && x >= leftLimit && x < rightLimit) {
                        _tileData[y - upperLimit][x - leftLimit] = null;
                    }
                }
            }
        }
        else {
            if (_layerData.data[0] == null) {
                Logger.error("Layer :: Layer <data> tag enclosed no data. (empty data tag)");
            }
            else {
                // We store the layer name
                _name = _layerData.@name;

                var tilesetID:int = 0;
                var tileX:int = 0;
                var tileY:int = 0;
                var encodedValue:String = _layerData.data[0];
                var decodedByteArray:ByteArray = Base64.decode(encodedValue);
                var outputByteArray:ByteArray;
                var calculatedIndex:int;

                decodedByteArray.uncompress();

                outputByteArray = decodedByteArray;

                for (y = 0; y < totalMapHeight; y++) {
                    if (y >= upperLimit && y < lowerLimit)
                        _tileData[y - upperLimit] = new Vector.<TileInfo>();
                    for (x = 0; x < totalMapWidth; x++) {
                        tilesetID = 0;
                        tileX = 0;
                        tileY = 0;

                        calculatedIndex = y * totalMapWidth;
                        calculatedIndex += x;
                        calculatedIndex *= 6;

                        tilesetID |= (outputByteArray[calculatedIndex]);
                        tilesetID |= (outputByteArray[calculatedIndex + 1] << 8 );

                        tileX |= (outputByteArray[calculatedIndex + 2]);
                        tileX |= (outputByteArray[calculatedIndex + 3] << 8 );

                        tileY |= (outputByteArray[calculatedIndex + 4]);
                        tileY |= (outputByteArray[calculatedIndex + 5] << 8 );

                        if (y >= upperLimit && y < lowerLimit && x >= leftLimit && x < rightLimit) {
                            if (tilesetID == 0 || tileX < 0 || tileY < 0) {
                                _tileData[y - upperLimit][x - leftLimit] = null;
                            }
                            else {
                                var tileset:ITileset = _associatedMap.getTileset(tilesetID);
                                if (tileset != null)
                                    _tileData[y - upperLimit][x - leftLimit] = new TileInfo(tileset, tileX, tileY);
                                else
                                    _tileData[y - upperLimit][x - leftLimit] = null;
                            }
                        }
                    }
                }
            }
        }

        if(_layerType == LayerType.UPPER_TYPE)
        {
            var mapWidth : int = _associatedMap.widthInTile;
            var mapHeight : int = _associatedMap.heightInTile;
            var colorWalk:uint = 0x000001;
            var colorUnWalk:uint = 0x000002;
            var image:BitmapData = new BitmapData(mapWidth, mapHeight, false);
            for (x = 0; x < mapWidth; x++) {
                for (y = 0; y < mapHeight; y++) {
                    if (_tileData[y][x] != null)
                        image.setPixel(x, y, colorWalk);
                    else
                        image.setPixel(x, y, colorUnWalk);
                }
            }
            var currentColor:uint = 0x000003;
            for (x = 0; x < mapWidth; x++) {
                for (y = 0; y < mapHeight; y++) {
                    if (image.getPixel(x, y) == colorWalk) {
                        image.floodFill(x, y, currentColor);
                        currentColor++;
                    }
                }
            }
            _connexRegion = new Vector.<Vector.<int>>(mapHeight);
            _connexMap = new Dictionary();
            for (y = 0; y < mapHeight; y++) {
                _connexRegion[y] = new Vector.<int>(mapWidth);
                for (x = 0; x < mapWidth; x++) {
                    currentColor = image.getPixel(x, y);
                    _connexRegion[y][x] = currentColor;
                    if(_connexMap[currentColor] == null)
                        _connexMap[currentColor] = new Vector.<IntPoint>();
                    _connexMap[currentColor].push(new IntPoint(x,  y));
                }
            }
            _connexMap[2] = null;
        }
    }

    //----------------------------------
    // shift
    //----------------------------------

    public function shift(xDir:int, yDir:int):void {
        var mapHeight:int = _associatedMap.heightInTile;
        var mapWidth:int = _associatedMap.widthInTile + xDir;
        var x:int;
        var y:int;

        for (y = 0; y < mapHeight; y++) {
            for (x = 0; x < xDir; x++) {
                _tileData[y].splice(0, 0, null);
            }
        }

        for (y = 0; y < yDir; y++) {
            var newTileY:Vector.<TileInfo> = new Vector.<TileInfo>();
            for (x = 0; x < mapWidth; x++) {
                newTileY[x] = null;
            }
            _tileData.splice(0, 0, newTileY);
        }
    }

    //----------------------------------
    // append
    //----------------------------------

    public function append(xDir:int, yDir:int):void {
        var mapHeight:int = _associatedMap.heightInTile;
        var mapWidth:int = _associatedMap.widthInTile + xDir;
        var x:int;
        var y:int;

        for (y = 0; y < mapHeight; y++) {
            for (x = 0; x < xDir; x++) {
                _tileData[y].push(null);
            }
        }

        for (y = 0; y < yDir; y++) {
            var newTileY:Vector.<TileInfo> = new Vector.<TileInfo>();
            for (x = 0; x < mapWidth; x++) {
                newTileY[x] = null;
            }
            _tileData.push(newTileY);
        }
    }

    //----------------------------------
    // getInfo
    //----------------------------------

    public function getInfo(x:int, y:int):TileInfo {
        return _tileData[y][x];
    }

    //----------------------------------
    // getConnexRect
    //----------------------------------

    public function getConnexRect(x:int, y:int):Vector.<IntPoint>
    {
        var currentRegion : int = _connexRegion[y][x];
        return _connexMap[currentRegion];
    }
}
}